#!/bin/python
# -*- coding: utf-8 -*-
"""
Name: cpp_builder.py
Author: harrybao
Mail: harrybao@123u.com
Time: 2021/07/12 17:34
Desc: C++项目构建
"""

import os
import json
import platform
from path_util import PathUtil

global g_path_util
g_path_util = PathUtil()

def json_has_attr(json_config, attr, print_error=True):
    if attr in json_config and json_config[attr] is not None:
        if print_error:
            print('json attr not exist: {0}'.format(attr))
        return True
    return False

class CppBuilder:
    """C++构建类"""
    def __init__(self, project_name='sdk-framework'):
        self._proj_name = project_name
        pass
    
    def generate_cmake(self):
        """生成CMakeLists.txt"""
        include_path_list = []
        linking_path_list = []
        self.get_include_path_list(include_path_list)
        self.get_linking_path_list(linking_path_list)

        cmake_file = open(os.path.join(g_path_util.sdk_root_path, 'src/CMakeLists.txt'), 'w')
        
        cmake_file.write('cmake_minimum_required(VERSION 3.5)\n')
        cmake_file.write('set(CMAKE_CXX_STANDARD 17)\n')
        cmake_file.write('project({0})\n'.format(self._proj_name))

        cmake_file.write('aux_source_directory(link_util SRC_FILE_LIST)\n')
        cmake_file.write('list(APPEND SRC_FILE_LIST link_util_api.cpp)\n')
        cmake_file.write('add_library(${PROJECT_NAME} SHARED ${SRC_FILE_LIST})\n')
        
        #############################################
        include_path_list.append('thirdparty')
        include_path_list.append('thirdparty/rpc-backend-cpp/src')
        # include_directories
        cmake_file.write('list(APPEND INCLUDE_DIR\n')
        for include_path in include_path_list:
            cmake_file.write('    ${{PROJECT_SOURCE_DIR}}/../{0}\n'.format(include_path))
        cmake_file.write(')\n')
        
        cmake_file.write('include_directories(${INCLUDE_DIR})\n')
        #############################################
        
        cmake_file.write('target_compile_definitions(${PROJECT_NAME} PUBLIC -DLINK_UTIL_EXPORT)\n')

        cmake_file.write('IF (MSVC)\n')

        cmake_file.write('    foreach(var\n')
        cmake_file.write('        CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE\n')
        cmake_file.write('        CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO\n')
        cmake_file.write('        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE\n')
        cmake_file.write('        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO\n')
        cmake_file.write('        )\n')
        cmake_file.write('        if(${var} MATCHES "/MD")\n')
        cmake_file.write('            string(REGEX REPLACE "/MD" "/MT" ${var} "${${var}}")\n')
        cmake_file.write('        endif()\n')
        cmake_file.write('    endforeach()\n')

        cmake_file.write('  target_link_libraries(${PROJECT_NAME}\n')
        cmake_file.write('      optimized ${PROJECT_SOURCE_DIR}/../lib/root/Release/librpc.a debug ${PROJECT_SOURCE_DIR}/../lib/root/Debug/librpc.a\n')
        cmake_file.write('      optimized ${PROJECT_SOURCE_DIR}/../lib/Windows/Release/libprotobuf.lib debug ${PROJECT_SOURCE_DIR}/../lib/Windows/Debug/libprotobufd.lib\n')
        cmake_file.write('      optimized ${PROJECT_SOURCE_DIR}/../lib/Windows/Release/libknet.a debug ${PROJECT_SOURCE_DIR}/../lib/Windows/Debug/libknet.a\n')
        cmake_file.write('      wldap32.lib\n')
        cmake_file.write('      ws2_32.lib\n')
        cmake_file.write('      psapi.lib\n')
        cmake_file.write('  )\n')
        
        #############################################
        # link lib
        cmake_file.write('    list(APPEND LINK_LIB "\n')
        for linking_path in linking_path_list:
            cmake_file.write('        /WHOLEARCHIVE:${{PROJECT_SOURCE_DIR}}/../{0}\n'.format(linking_path))
        cmake_file.write('    ")\n')
        
        cmake_file.write('    SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINK_FLAGS_DEBUG ${LINK_LIB})\n')
        cmake_file.write('    SET_TARGET_PROPERTIES(${PROJECT_NAME} PROPERTIES LINK_FLAGS_RELEASE ${LINK_LIB})\n')
        ##############################################

        cmake_file.write('ELSE()\n')

        cmake_file.write('  if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")\n')
        cmake_file.write('      SET(CMAKE_CXX_FLAGS "-m64 -g -O2 -std=c++17 -Wall -fnon-call-exceptions -fPIC")\n')
        cmake_file.write('      set(CMAKE_CXX_COMPILER "clang++")\n')
        cmake_file.write('  elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")\n')
        cmake_file.write('      SET(CMAKE_CXX_FLAGS "-m64 -g -O2 -std=c++17 -Wall -fnon-call-exceptions -fPIC")\n')
        cmake_file.write('      set(CMAKE_CXX_COMPILER "g++")\n')
        cmake_file.write('  endif()\n')

        cmake_file.write('  target_link_libraries(${PROJECT_NAME}\n')
        cmake_file.write('  -Wl,--whole-archive\n')
        for linking_path in linking_path_list:
            cmake_file.write('      ${{PROJECT_SOURCE_DIR}}/../{0}\n'.format(linking_path))
        cmake_file.write('  -Wl,--no-whole-archive\n')
        cmake_file.write('      ${PROJECT_SOURCE_DIR}/../lib/root/librpc.a\n')
        cmake_file.write('      optimized ${PROJECT_SOURCE_DIR}/../lib/Linux/libprotobuf.a debug ${PROJECT_SOURCE_DIR}/../lib/Linux/libprotobuf.a\n')
        cmake_file.write('      optimized ${PROJECT_SOURCE_DIR}/../lib/Linux/libknet.a debug ${PROJECT_SOURCE_DIR}/../lib/Linux/libknet.a\n')
        cmake_file.write('      -lpthread\n')
        cmake_file.write('      -ldl\n')
        cmake_file.write('  )\n')
        
        cmake_file.write('ENDIF()\n')
 
        return True
    
    def build_sdk(self):
        """编译SDKFrame"""
        if platform.system() == 'Windows':
            cache_file_path = '{0}/CMakeCache.txt'.format(g_path_util.sdk_winproj_path)
            if g_path_util.check_path(cache_file_path, False):
                os.remove(cache_file_path)

            if os.system('cmake {0} -G "Visual Studio 16 2019" -A x64 -B{1}'.format(
                g_path_util.sdk_src_path, g_path_util.sdk_winproj_path)) != 0:
                return False
            if os.system(
                'cmake --build {0} --config "Release"'.format(g_path_util.sdk_winproj_path)) != 0:
                return False
            # if os.system('cmake --build {0} --config "Debug"'.format(g_path_util.sdk_winproj_path)) != 0:
            #    return False
        else:
            build_path = '{0}/build'.format(g_path_util.sdk_src_path)
            if not g_path_util.check_path(build_path, False):
                os.makedirs(build_path)

            if os.system(
                'cmake {0} -G "Unix Makefiles" -B{1}'.format(g_path_util.sdk_src_path, build_path)) != 0:
                return False
            if os.system(
                'cmake --build {0} --config "Release"'.format(build_path)) != 0:
                return False
            os.system('cmake --build {0} --config "Debug"'.format(build_path))
        return True
    
    def get_include_path_list(self, include_path_list):
        """获取头文件目录列表"""
        include_path_set = set()
        config = json.load(open(os.path.join(g_path_util.sdk_root_path, 'package.json'), 'r', encoding='utf-8'))
        for item in config['service']:
            package_info_path = os.path.join(
                g_path_util.sdk_package_path, 'info', item['name'], 'package.json')
            if not g_path_util.check_path(package_info_path):
                continue

            package_info_json = json.load(open(package_info_path, 'r', encoding='utf-8'))

            if package_info_json['version'] != item['version']:
                print('check version not match: {0} - {1}'.format(
                    package_info_json['version'], item['version']))
                continue

            for idl_name, idl_value in item['idl'].items():
                for service_name in idl_value['service']:
                    print('package/include/struct/{0}/{1}'.format(item['name'], idl_name))
                    print('package/include/proxy/{0}/{1}/{2}'.format(item['name'], idl_name, service_name))
                    include_path_set.add(
                        'package/include/struct/{0}/{1}'.format(item['name'], idl_name))
                    include_path_set.add(
                        'package/include/proxy/{0}/{1}/{2}'.format(item['name'], idl_name, service_name))

            include_path_list += list(include_path_set)
            
    def get_linking_path_list(self, linking_path_list):
        """获取链接库列表"""
        linking_path_set = set()
        config = json.load(open(os.path.join(g_path_util.sdk_root_path, 'package.json'), 'r', encoding='utf-8'))
        for item in config['service']:
            package_info_path = os.path.join(
                g_path_util.sdk_package_path, 'info', item['name'], 'package.json')
            if not g_path_util.check_path(package_info_path):
                continue

            # json -> package/info/json
            package_info_json = json.load(open(package_info_path, 'r', encoding='utf-8'))

            if package_info_json['version'] != item['version']:
                print('check version not match: {0} - {1}'.format(
                    package_info_json['version'], item['version']))
                continue

            # idl_name'guildservicebasic'
            # service_name:'GuildServicBasic'
            # include_path:[
            #   'package/include/proxy/GuildService/guildservicebasic/GuildServiceBasic',
            #   'package/include/struct/GuildService/guildservicebasic'
            # ]
            for idl_name, idl_value in item['idl'].items():
                for service_name in idl_value['service']:
                    if not json_has_attr(package_info_json['idl'], idl_name):
                        continue
                    json_service_dic = package_info_json['idl'][idl_name]['service']

                    for proxy_lib_name in json_service_dic[service_name]['proxyLib']:
                        linking_path_set.add('package/proxy/{0}/{1}'.format(item['name'], proxy_lib_name))

                for protobuf_lib_name in json_service_dic['protobuf']:
                    linking_path_set.add('package/protobuf/{0}/{1}'.format(item['name'], protobuf_lib_name))
            linking_path_list += list(set(linking_path_set))


    def build_demo(self):
        """构建Demo"""
        build_path = '{0}/build'.format(g_path_util.sdk_demo_path)
        if platform.system() == 'Windows':
            cache_file_path = '{0}/CMakeCache.txt'.format(build_path)
            if g_path_util.check_path(cache_file_path, False):
                os.remove(cache_file_path)

            if os.system('cmake {0} -G "Visual Studio 16 2019" -A x64 -B{1}'.format(
                g_path_util.sdk_demo_path, build_path)) != 0:
                return False
            if os.system(
                'cmake --build {0} --config "Release"'.format(build_path)) != 0:
                return False
            if os.system('cmake --build {0} --config "Debug"'.format(build_path)) != 0:
               return False
        else:
            if not g_path_util.check_path(build_path, False):
                os.makedirs(build_path)

            if os.system(
                'cmake {0} -G "Unix Makefiles" -B{1}'.format(g_path_util.sdk_demo_path, build_path)) != 0:
                return False
            if os.system(
                'cmake --build {0} --config "Release"'.format(build_path)) != 0:
                return False
            # os.system('cmake --build {0} --config "Debug"'.format(build_path))
        return True
